// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package gardener

import (
	"context"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/selection"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/client-go/rest"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
	resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1"
	"github.com/gardener/gardener/pkg/resourcemanager/controller/garbagecollector/references"
	"github.com/gardener/gardener/pkg/utils"
	kubernetesutils "github.com/gardener/gardener/pkg/utils/kubernetes"
	"github.com/gardener/gardener/pkg/utils/secrets"
)

// Domain contains information about a domain configured in the garden cluster.
type Domain struct {
	Domain     string
	Provider   string
	Zone       string
	SecretData map[string][]byte
}

func constructDomainFromSecret(secret *corev1.Secret) (*Domain, error) {
	provider, domain, zone, err := GetDomainInfoFromAnnotations(secret.Annotations)
	if err != nil {
		return nil, err
	}

	return &Domain{
		Domain:     domain,
		Provider:   provider,
		Zone:       zone,
		SecretData: secret.Data,
	}, nil
}

// DomainIsDefaultDomain identifies whether the given domain is a default domain.
func DomainIsDefaultDomain(domain string, defaultDomains []*Domain) *Domain {
	for _, defaultDomain := range defaultDomains {
		if strings.HasSuffix(domain, "."+defaultDomain.Domain) {
			return defaultDomain
		}
	}
	return nil
}

var gardenRoleReq = utils.MustNewRequirement(v1beta1constants.GardenRole, selection.Exists)

// ReadGardenInternalDomain reads the internal domain information from the Garden cluster.
func ReadGardenInternalDomain(
	ctx context.Context,
	c client.Reader,
	namespace string,
	enforceSecret bool,
	seedDNSProvider *gardencorev1beta1.SeedDNSProviderConfig,
) (
	*Domain,
	error,
) {
	if seedDNSProvider != nil {
		secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
			Name:      seedDNSProvider.CredentialsRef.Name,
			Namespace: seedDNSProvider.CredentialsRef.Namespace,
		}}

		if err := c.Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil {
			return nil, fmt.Errorf("cannot fetch internal domain secret: %w", err)
		}

		return &Domain{
			Domain:     seedDNSProvider.Domain,
			Provider:   seedDNSProvider.Type,
			Zone:       ptr.Deref(seedDNSProvider.Zone, ""),
			SecretData: secret.Data,
		}, nil
	}

	secret, err := ReadInternalDomainSecret(ctx, c, namespace, enforceSecret)
	if err != nil || secret == nil && !enforceSecret {
		return nil, err
	}

	domain, err := constructDomainFromSecret(secret)
	if err != nil {
		return nil, fmt.Errorf("error constructing internal domain from secret %s: %w", client.ObjectKeyFromObject(secret), err)
	}

	return domain, nil
}

// ReadGardenDefaultDomains reads the default domain information from the Garden cluster.
func ReadGardenDefaultDomains(
	ctx context.Context,
	c client.Reader,
	namespace string,
	seedDNSDefaults []gardencorev1beta1.SeedDNSProviderConfig,
) (
	[]*Domain,
	error,
) {
	var domains []*Domain

	if len(seedDNSDefaults) > 0 {
		for _, seedDNSDefault := range seedDNSDefaults {
			secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
				Name:      seedDNSDefault.CredentialsRef.Name,
				Namespace: seedDNSDefault.CredentialsRef.Namespace,
			}}

			if err := c.Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil {
				return nil, fmt.Errorf("cannot fetch default domain secret %s: %w", client.ObjectKeyFromObject(secret), err)
			}

			domain := &Domain{
				Domain:     seedDNSDefault.Domain,
				Provider:   seedDNSDefault.Type,
				Zone:       ptr.Deref(seedDNSDefault.Zone, ""),
				SecretData: secret.Data,
			}
			domains = append(domains, domain)
		}

		return domains, nil
	}

	// Fall back to reading default domain secrets from the namespace
	secrets, err := ReadGardenDefaultDomainsSecrets(ctx, c, namespace)
	if err != nil {
		return nil, err
	}

	for _, secret := range secrets {
		domain, err := constructDomainFromSecret(&secret)
		if err != nil {
			return nil, fmt.Errorf("error constructing default domain from secret %s: %w", client.ObjectKeyFromObject(&secret), err)
		}
		domains = append(domains, domain)
	}

	return domains, nil
}

// ReadGardenDefaultDomainsSecrets reads the default domain secrets from the given namespace.
// This function makes sense only if no default domains are configured in the seed spec.
// The passed reader should target the garden cluster.
//
// Deprecated: Use ReadGardenDefaultDomains instead.
func ReadGardenDefaultDomainsSecrets(
	ctx context.Context,
	c client.Reader,
	namespace string,
) (
	[]corev1.Secret,
	error,
) {
	// TODO(dimityrmirchev): Remove this function once explicit DNS configuration becomes mandatory after release v1.133
	secretList := &corev1.SecretList{}
	if err := c.List(ctx, secretList, client.InNamespace(namespace), client.MatchingLabels{
		v1beta1constants.GardenRole: v1beta1constants.GardenRoleDefaultDomain,
	}); err != nil {
		return nil, err
	}

	// Sort domain secrets by DNSDefaultDomainPriority to get the domain with the highest priority first
	sort.SliceStable(secretList.Items, func(i, j int) bool {
		iAnnotations := secretList.Items[i].GetAnnotations()
		jAnnotations := secretList.Items[j].GetAnnotations()
		var iPriority, jPriority int
		var err error

		if iAnnotations != nil {
			if domainPriority, ok := iAnnotations[DNSDefaultDomainPriority]; ok {
				iPriority, err = strconv.Atoi(domainPriority)
				if err != nil {
					iPriority = 0
				}
			}
		}
		if jAnnotations != nil {
			if domainPriority, ok := jAnnotations[DNSDefaultDomainPriority]; ok {
				jPriority, err = strconv.Atoi(domainPriority)
				if err != nil {
					jPriority = 0
				}
			}
		}

		return iPriority > jPriority
	})

	return secretList.Items, nil
}

// ReadInternalDomainSecret reads the internal domain secret from the given namespace.
// If enforceSecret is true, an error is returned if no secret is found.
// If enforceSecret is false, the function can return (nil, nil) in case no internal domain secret is found.
func ReadInternalDomainSecret(ctx context.Context, c client.Reader, namespace string, enforceSecret bool) (*corev1.Secret, error) {
	secretList := &corev1.SecretList{}
	if err := c.List(ctx, secretList, client.InNamespace(namespace), client.MatchingLabels{
		v1beta1constants.GardenRole: v1beta1constants.GardenRoleInternalDomain,
	}); err != nil {
		return nil, err
	}

	if len(secretList.Items) == 0 {
		// For each Shoot we create a LoadBalancer(LB) pointing to the API server of the Shoot. Because the technical address
		// of the LB (ip or hostname) can change we cannot directly write it into the kubeconfig of the components
		// which talk from outside (kube-proxy, kubelet etc.) (otherwise those kubeconfigs would be broken once ip/hostname
		// of LB changed; and we don't have means to exchange kubeconfigs currently).
		// Therefore, to have a stable endpoint, we create a DNS record pointing to the ip/hostname of the LB. This DNS record
		// is used in all kubeconfigs. With that we have a robust endpoint stable against underlying ip/hostname changes.
		// And there can only be one of this internal domain secret because otherwise the gardener would not know which
		// domain it should use.
		if enforceSecret {
			return nil, fmt.Errorf("need an internal domain secret but found none")
		}
		return nil, nil
	}

	if len(secretList.Items) > 1 {
		return nil, fmt.Errorf("found more than one internal domain secret")
	}

	return &secretList.Items[0], nil
}

// ReadGardenSecrets reads the Kubernetes Secrets from the Garden cluster which are independent of Shoot clusters.
// The Secret objects are stored on the Controller in order to pass them to created Garden objects later.
func ReadGardenSecrets(
	ctx context.Context,
	log logr.Logger,
	c client.Reader,
	namespace string,
) (
	map[string]*corev1.Secret,
	error,
) {
	var (
		logInfo                                  []string
		secretsMap                               = make(map[string]*corev1.Secret)
		numberOfAlertingSecrets                  = 0
		numberOfGlobalMonitoringSecrets          = 0
		numberOfShootServiceAccountIssuerSecrets = 0
	)

	secretList := &corev1.SecretList{}
	if err := c.List(ctx, secretList, client.InNamespace(namespace), client.MatchingLabelsSelector{Selector: labels.NewSelector().Add(gardenRoleReq)}); err != nil {
		return nil, err
	}

	for _, secret := range secretList.Items {
		// Retrieve the alerting secret to configure alerting. Either in cluster email alerting or
		// external alertmanager configuration.
		if secret.Labels[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleAlerting {
			authType := string(secret.Data["auth_type"])
			if authType != "smtp" && authType != "none" && authType != "basic" && authType != "certificate" {
				return nil, fmt.Errorf("invalid or missing field 'auth_type' in secret %s", secret.Name)
			}
			alertingSecret := secret
			secretsMap[v1beta1constants.GardenRoleAlerting] = &alertingSecret
			logInfo = append(logInfo, fmt.Sprintf("alerting secret %q", secret.Name))
			numberOfAlertingSecrets++
		}

		// Retrieving basic auth secret for aggregate monitoring with a label
		// indicating the Garden role global-monitoring.
		if secret.Labels[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleGlobalMonitoring {
			monitoringSecret := secret
			secretsMap[v1beta1constants.GardenRoleGlobalMonitoring] = &monitoringSecret
			logInfo = append(logInfo, fmt.Sprintf("monitoring basic auth secret %q", secret.Name))
			numberOfGlobalMonitoringSecrets++
		}

		// Retrieving basic auth secret for remote write monitoring with a label
		// indicating the Garden role global-shoot-remote-write-monitoring.
		if secret.Labels[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleGlobalShootRemoteWriteMonitoring {
			monitoringSecret := secret
			secretsMap[v1beta1constants.GardenRoleGlobalShootRemoteWriteMonitoring] = &monitoringSecret
			logInfo = append(logInfo, fmt.Sprintf("monitoring basic auth secret %q", secret.Name))
		}

		if secret.Labels[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleShootServiceAccountIssuer {
			shootIssuer := secret
			if hostname, ok := secret.Data["hostname"]; !ok {
				return nil, fmt.Errorf("cannot use Shoot Service Account Issuer secret %q as it does not contain key 'hostname'", secret.Name)
			} else if strings.TrimSpace(string(hostname)) == "" {
				return nil, fmt.Errorf("cannot use Shoot Service Account Issuer secret %q as it does contain an empty 'hostname' key", secret.Name)
			}
			secretsMap[v1beta1constants.GardenRoleShootServiceAccountIssuer] = &shootIssuer
			logInfo = append(logInfo, fmt.Sprintf("Shoot Service Account Issuer secret %q", secret.Name))
			numberOfShootServiceAccountIssuerSecrets++
		}
	}

	// Operators can configure gardener to send email alerts or send the alerts to an external alertmanager. If no configuration
	// is provided then no alerts will be sent.
	if numberOfAlertingSecrets > 1 {
		return nil, fmt.Errorf("can only accept at most one alerting secret, but found %d", numberOfAlertingSecrets)
	}

	if numberOfGlobalMonitoringSecrets > 1 {
		return nil, fmt.Errorf("can only accept at most one global monitoring secret, but found %d", numberOfGlobalMonitoringSecrets)
	}

	// The managed shoot service account issuer is configured centrally per Garden cluster.
	// The presence of more than one secret is an ambiguous behaviour and should be disallowed.
	if numberOfShootServiceAccountIssuerSecrets > 1 {
		return nil, fmt.Errorf("can only accept at most one shoot service account issuer secret, but found %d", numberOfShootServiceAccountIssuerSecrets)
	}

	log.Info("Found secrets", "namespace", namespace, "secrets", logInfo)
	return secretsMap, nil
}

const (
	// SecretNamePrefixGardenAccess is the prefix of all secrets containing credentials for accessing the garden cluster.
	SecretNamePrefixGardenAccess = "garden-access-"
	// VolumeMountPathGenericGardenKubeconfig is a constant for the path to which the generic garden kubeconfig will be mounted.
	VolumeMountPathGenericGardenKubeconfig = "/var/run/secrets/gardener.cloud/garden/generic-kubeconfig"
	// PathGardenToken is a constant for the path at which the garden token file is accessible.
	PathGardenToken = VolumeMountPathGenericGardenKubeconfig + "/" + resourcesv1alpha1.DataKeyToken
	// PathGenericGardenKubeconfig is a constant for the path at which the kubeconfig file is accessible.
	PathGenericGardenKubeconfig = VolumeMountPathGenericGardenKubeconfig + "/" + secrets.DataKeyKubeconfig
)

// NewGardenAccessSecret returns a new AccessSecret object and initializes it with an empty corev1.Secret object
// with the given name and namespace. If not already done, the name will be prefixed with the
// SecretNamePrefixGardenAccess. The ServiceAccountName field will be defaulted with the name.
func NewGardenAccessSecret(name, namespace string) *AccessSecret {
	if !strings.HasPrefix(name, SecretNamePrefixGardenAccess) {
		name = SecretNamePrefixGardenAccess + name
	}

	return &AccessSecret{
		Secret: &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: namespace,
			},
		},
		ServiceAccountName: strings.TrimPrefix(name, SecretNamePrefixGardenAccess),
		Class:              resourcesv1alpha1.ResourceManagerClassGarden,
	}
}

// InjectGenericGardenKubeconfig injects the volumes, volume mounts, and env vars for the generic garden kubeconfig into
// the provided object. The access secret name must be the name of a secret containing a JWT token which should be used
// by the kubeconfig. The mount path is important to match the token path configured in the generic kubeconfig.
// If containerNames are not specified, the adjustments happen for all containers.
// If any of the containers in the object already has the GARDEN_KUBECONFIG env var, the object is not mutated.
func InjectGenericGardenKubeconfig(obj runtime.Object, genericKubeconfigName, accessSecretName, mountPath string, containerNames ...string) error {
	// check for presence of env var
	hasGardenKubeconfig := false

	if err := kubernetesutils.VisitPodSpec(obj, func(podSpec *corev1.PodSpec) {
		kubernetesutils.VisitContainers(podSpec, func(container *corev1.Container) {
			if kubernetesutils.HasEnvVar(*container, v1beta1constants.EnvGenericGardenKubeconfig) {
				hasGardenKubeconfig = true
			}
		}, containerNames...)
	}); err != nil {
		return err
	}

	if hasGardenKubeconfig {
		return nil
	}

	// inject volume and volumeMounts
	if err := injectGenericKubeconfig(
		obj,
		genericKubeconfigName,
		accessSecretName,
		"garden-kubeconfig",
		mountPath,
		containerNames...,
	); err != nil {
		return err
	}

	// inject reference annotations for generic kubeconfig
	if err := references.InjectAnnotations(obj); err != nil {
		return err
	}

	// inject env var
	return kubernetesutils.VisitPodSpec(obj, func(podSpec *corev1.PodSpec) {
		kubernetesutils.VisitContainers(podSpec, func(container *corev1.Container) {
			kubernetesutils.AddEnvVar(container, corev1.EnvVar{
				Name:  v1beta1constants.EnvGenericGardenKubeconfig,
				Value: mountPath + "/" + secrets.DataKeyKubeconfig,
			}, true)
		}, containerNames...)
	})
}

// PrepareGardenClientRestConfig takes a base rest config and adds an optional host and CA certificate.
func PrepareGardenClientRestConfig(baseConfig *rest.Config, address *string, caCert []byte) *rest.Config {
	gardenClientRestConfig := rest.CopyConfig(baseConfig)
	if address != nil {
		gardenClientRestConfig.Host = *address
	}
	if caCert != nil {
		gardenClientRestConfig.TLSClientConfig = rest.TLSClientConfig{
			CAData: caCert,
		}
	}
	return gardenClientRestConfig
}

// DefaultGardenerGVKsForEncryption returns the list of [schema.GroupVersionKind] served by Gardener API Server which are encrypted by default.
func DefaultGardenerGVKsForEncryption() []schema.GroupVersionKind {
	return []schema.GroupVersionKind{
		gardencorev1beta1.SchemeGroupVersion.WithKind("ControllerDeployment"),
		gardencorev1beta1.SchemeGroupVersion.WithKind("ControllerRegistration"),
		gardencorev1beta1.SchemeGroupVersion.WithKind("InternalSecret"),
		gardencorev1beta1.SchemeGroupVersion.WithKind("ShootState"),
	}
}

// DefaultGardenerGroupResourcesForEncryption returns the list of [schema.GroupResource] served by Gardener API Server which are encrypted by default.
func DefaultGardenerGroupResourcesForEncryption() []schema.GroupResource {
	return []schema.GroupResource{
		gardencorev1beta1.Resource("controllerdeployments"),
		gardencorev1beta1.Resource("controllerregistrations"),
		gardencorev1beta1.Resource("internalsecrets"),
		gardencorev1beta1.Resource("shootstates"),
	}
}

// DefaultGardenerResourcesForEncryption returns the list of resources served by Gardener API Server which are encrypted by default.
func DefaultGardenerResourcesForEncryption() sets.Set[string] {
	return sets.New(
		gardencorev1beta1.Resource("controllerdeployments").String(),
		gardencorev1beta1.Resource("controllerregistrations").String(),
		gardencorev1beta1.Resource("internalsecrets").String(),
		gardencorev1beta1.Resource("shootstates").String(),
	)
}

// GetGardenWildcardCertificate gets the wildcard TLS certificate for the Garden runtime ingress and SNI domains.
// Nil is returned if no wildcard certificate is configured.
func GetGardenWildcardCertificate(ctx context.Context, c client.Client, namespace string) (*corev1.Secret, error) {
	return getWildcardCertificate(ctx, c, namespace, v1beta1constants.GardenRoleGardenWildcardCert)
}

// GetRequiredGardenWildcardCertificate gets the wildcard TLS certificate for the Garden runtime ingress and SNI domains.
// An error is returned if no wildcard certificate is found.
func GetRequiredGardenWildcardCertificate(ctx context.Context, c client.Client, namespace string) (*corev1.Secret, error) {
	tlsSecret, err := GetGardenWildcardCertificate(ctx, c, namespace)
	if err != nil {
		return nil, fmt.Errorf("failed to get garden wildcard certificate secret: %w", err)
	}
	if tlsSecret == nil {
		return nil, fmt.Errorf("no garden wildcard certificate secret found")
	}

	return tlsSecret, nil
}
